﻿
#ifndef ORTHO_H
#define ORTHO_H

//{{{ file documentation -------------------------------------------------------

/*!
 * @file     ortho.h
 * @brief    dicodor "orthographe" class declaration
 */

//}}} --------------------------------------------------------------------------
//{{{ includes / predeclarations -----------------------------------------------

// @(#) $Id: ortho.h 53 2012-05-23 13:56:41Z nsouchon-dico@altern.org $

#include "config.h"

// #include <QtCore/QFile>
// #include <QtCore/QDir>
// #include <QtCore/QHash>
// #include <QtCore/QStringList>

#include <QtCore/QSet>
#include <QtCore/QDebug>
#include <QtCore/QTextStream>
#include <QtCore/QObject>

//}}} --------------------------------------------------------------------------
//{{{ structures ---------------------------------------------------------------
//}}} --------------------------------------------------------------------------
//{{{ .Ortho -------------------------------------------------------------------

class Ortho                           : public QObject
{
    Q_OBJECT;

public:
    explicit                            Ortho (QObject * parent = 0);
    virtual                             ~Ortho ();

    bool                                init ();

    QStringList                         availableLanguages ();                             // langages disponibles
    const QString                     & currentLanguage ();

    const QStringList                 & personalDic ();
    const QHash<QString, QVariant>    & translateDic ();
    QStringList                         translateDicAsList ();

    bool                                isUnknown (const QString & word);
    QStringList                         suggestsFor (const QString & word);               // liste des suggestions pour le mot inconnu word
    QStringList                         unknownWords (const QString & text);              // liste des mots inconnus dans text

    int                                 weirdCount (const QString & word);                // nombre de caractères absents du dico
    const QStringList                   decomp (const QString & word);                    // décomposition du mot

    QString                             soundex (const QString & word)  const;
    QString                             sonex (const QString & word)    const;

public slots:
    // réglages
    void                                _setCurrentLanguage (const QString & lang); 
    void                                setCurrentLanguage (const QString & lang); 
    void                                setDistanceMax (const int m_distance); 
    void                                setWeirdMax (const int max); 
    void                                setLengthMin (const int min); 

    // gestion des dicos
    void                                addToIgnoreList (const QString & word); 
    void                                addLineToPersonalDic (const QString & word); 
    void                                removeLineFromPersonalDic (const QString & word); 
    void                                clearPersonalDic ();
    void                                addLineToTranslateDic (const QString & word, const QString & translation); 
    void                                removeLineFromTranslateDic (const QString &line); 
    void                                clearTranslateDic ();
    void                                mergeToPersonalDic (const QString & fileName); 

    // auto correction
    QString                             translate (const QString & text); 
    QString                             autoSpelling (const QString & text); 
    void                                stopAutoSpelling ();

signals:
    void                                message (const QString & msg); 
    void                                personalDicHasChanged ();
    void                                translateDicHasChanged ();
    void                                autoSpellingCount (int); 
    void                                autoSpellingAdvance (int); 

private:
    QChar                               soundexCode (const QChar & car)  const;

    static bool                         isOnlyDigits (const QString & word);              // le mot n'est que formé de chiffres
    static QString                      toFirstCap (const QString & word);                // retourne le mot avec l'initiale en maj.
    static bool                         isFirstCap (const QString & word);                // le mot a-t-il une initiale majuscule ?
    static bool                         isAllCap (const QString & word);                  // le mot est-il tout en majuscules ?
    static const QString                separator ();                                     // séparateur pour les remplacements auto

    int                                 dlDistance (const QString & s1,	                  // distance de Damerau-Levenstein
			/**/                                    const QString & s2, 
			/**/                                    const bool dam, 
			/**/                                    const int md = 0xfff);

    bool                                loadDic (const QString & lang);                   // charge le dico "officiel"
    void                                loadPersonalDic ();                               // charge le dico personnel
    void                                buildHashs ();                                    // construit les tables de hachage
    void                                prepareHash (Config::DicDesc & desc);
    void                                savePersonalDic ();                               // sauve le dico perso
    void                                loadTranslateDic ();                              // charge
    void                                saveTranslateDic ();

    bool                                dicContains (const QString & word)  const;        // word fait-il partie du dico ?

    int                                 distanceMax (const int len); 

    void                                sendMessage (const QString & msg); 

    QString                           & pushCode (const QString & code, QString & current)  const;
    QString                           & popCode (QString & currentCode)                     const;
    QString                             ordered (const QString& word)                       const;

private:
    Config                            * m_config;

    QHash<QString, QVariant>            m_translateDic;                                   // auto-corrections
    QStringList                         m_personalDic;                                    // dictionnaire personnel
    QSet<QString>                       m_wordSet;                                        // dico officiel sous forme de QSet
    QHash<QString, QStringList>         m_wordHash;                                       // dico implémenté sous forme de hash --> key=soundex ou sonex2
    QHash<QChar, QChar>                 m_soundexCode;                                    // hash table
    // QHash<QString, QString>          m_convertionTable;
    QStringList                         m_ignoreList;                                     // liste des mots à ignorer
    QSet<QChar>                         m_charSet;                                        // liste des différents caractères contenus ds le dico
    QString                             m_dicsPath;                                       // répertoire contenant les dicos
    QHash<QString, QString>             m_availableLanguages;                             // langues disponibles

    QString                             m_currentLanguage;                                // langue courante
    int                                 m_lengthMin;                                      // longueur minimale d'un mot
    int                                 m_weirdCountMax;                                  // nbre de caractères "bizarres" maxi
    int                                 m_distanceMax;                                    // distance de Damerau-Levenshtein (ddl) maxi
    int                                 m_distance[256][256];                             // tableau de calcul de la ddl
    bool                                m_autoSpellingStop;
};

//}}} --------------------------------------------------------------------------
//{{{ inline / template functions ----------------------------------------------
//}}} --------------------------------------------------------------------------
//{{{ .QChar                            soundexCode (const QChar & car) const --

inline QChar Ortho::soundexCode (const QChar& car) const
{
    return m_soundexCode.value (car);
}

//}}} --------------------------------------------------------------------------
//{{{ .bool                             isOnlyDigits (const QString & word) ----

inline bool Ortho::isOnlyDigits (const QString& word)
{
    bool ok; word.toInt (&ok, 10); return ok;
}

//}}} --------------------------------------------------------------------------
//{{{ .QString                          toFirstCap (const QString & word) ------

inline QString Ortho::toFirstCap (const QString& word)
{
    return word.at (0).toUpper () + word.mid (1).toLower ();
}

//}}} --------------------------------------------------------------------------
//{{{ .bool                             isFirstCap (const QString & word) ------

inline bool Ortho::isFirstCap (const QString& word)
{
    return word == toFirstCap (word);
}

//}}} --------------------------------------------------------------------------
//{{{ .bool                             isAllCap (const QString & word) --------

inline bool Ortho::isAllCap (const QString& word)
{
    return word == word.toUpper ();
}

//}}} --------------------------------------------------------------------------
//{{{ .const QString                    separator () ---------------------------

inline const QString Ortho::separator ()
{
    return QString (" --> ");
}

//}}} --------------------------------------------------------------------------
//{{{ .bool                             dicContains (const QString & word) const -

inline bool Ortho::dicContains (const QString& word) const
{
    const bool b = m_wordSet.contains (word)
        || m_wordSet.contains (word.toLower ())
        || m_wordSet.contains (toFirstCap (word))
        || m_wordSet.contains (word.toUpper ())
        ;
    return b;
}

//}}} --------------------------------------------------------------------------
//{{{ .QString                        & pushCode (const QString & code, QString & current) const -

inline QString& Ortho::pushCode (const QString& code, QString& current) const
{
    return current.prepend (code.left (code.length ()
                - (code.right (1) == current.left (1) ? 1 : 0)));
}

//}}} --------------------------------------------------------------------------
//{{{ .QString                        & popCode (QString & currentCode) const --

inline QString& Ortho::popCode (QString& currentCode) const
{
    return currentCode.remove (0, 1);
}

//}}} --------------------------------------------------------------------------
//{{{ .const QString                  & currentLanguage () ---------------------

inline const QString& Ortho::currentLanguage ()
{
    return m_currentLanguage;
}

//}}} --------------------------------------------------------------------------
//{{{ .bool                             isUnknown (const QString & word) -------

inline bool Ortho::isUnknown (const QString& word)
{
    if (word.length () < m_lengthMin) return false;
    if (m_ignoreList.contains (word)) return false;
    if (isOnlyDigits (word)) return false;
    if (weirdCount (word) > m_weirdCountMax) return true;;
    if (dicContains (word)) return false;
    return true;
}

//}}} --------------------------------------------------------------------------
//{{{ .QStringList                      availableLanguages () ------------------

inline QStringList Ortho::availableLanguages ()
{
    return m_availableLanguages.keys ();
}

//}}} --------------------------------------------------------------------------
//{{{ .const QStringList              & personalDic () -------------------------

inline const QStringList& Ortho::personalDic ()
{
    return m_personalDic;
}

//}}} --------------------------------------------------------------------------
//{{{ .const QHash<QString, QVariant> & translateDic () ------------------------

inline const QHash<QString, QVariant>& Ortho::translateDic ()
{
    return m_translateDic;
}

//}}} --------------------------------------------------------------------------
//{{{ .void                             setWeirdMax (const int max) ------------

// as the setter causes, de facto, m_weirdCountMax to be public,
// why is m_weirdCountMax not simpli public ?

inline void Ortho::setWeirdMax (const int max)
{
    m_weirdCountMax = max;
}

//}}} --------------------------------------------------------------------------
//{{{ .void                             setLengthMin (const int min) -----------

// as the setter causes, de facto, m_lengthMin to be public,
// why is m_lengthMin not simpli public ?

inline void Ortho::setLengthMin (const int min)
{
    m_lengthMin = min;
}

//}}} --------------------------------------------------------------------------
//{{{ .void                             addToIgnoreList (const QString & word) -

inline void Ortho::addToIgnoreList (const QString& word)
{
    if (word.isEmpty ()) return;
    m_ignoreList << word;
    m_ignoreList.removeDuplicates ();
}

//}}} --------------------------------------------------------------------------
//{{{ .void                             stopAutoSpelling () --------------------

inline void Ortho::stopAutoSpelling ()
{
    m_autoSpellingStop = true;
}

//}}} --------------------------------------------------------------------------
//{{{ .QString                          ordered (const QString & word) const ---

inline QString Ortho::ordered (const QString& word) const
{
    QString result (word);
    qSort (result.begin (), result.end ());
    return result;
}

//}}} --------------------------------------------------------------------------

#endif // ORTHO_H

